###########
# Symbols #
###########

# prefix by a colon

:a
:b
:utf
:anything

# Hashtables

my_hash_table = {:a => 1, :b => 2, :c => 3}
my_hash_table[:b]
# => 2

######################
# Yielding to blocks #
######################

# + we can add a method to a class at runtime

# + self

# + yield yields its arguments to a block

class Integer
  def custom_each
    i = 0
    while i < self
      i = i + 1
      yield i
    end
  end
end

3.custom_each do |num| puts "num: #{num}" end

# Yay, I was right!

# Can yield yield more than 1 thing?

class Integer
  def custom_each_and_square
    i = 0
    while i < self
      i = i + 1
      yield i, i*i
    end
  end
end

3.custom_each_and_square do
  |num, squared|
  puts "num: #{num}, squared: #{squared}"
end

# Boom!

# num: 1, squared: 1
# num: 2, squared: 4
# num: 3, squared: 9

#######################
# Blocks as arguments #
#######################

# From playing around:

# (Proc.new do |a| puts "arg: #{a}" end).call(1)
# (lambda do |a| puts "arg: #{a}" end).call(1)

# And it seems blocks work similarly with a call to `call`:

def call_block (&block)
  block.call('test')
end

# What does the `&` mean? "dereference pointer"?

val_in_environment = 3
call_block do
  |sth, val_in_environment|
  puts "Hello, block! #{sth} #{val_in_environment}"
end

# Basically blocks are lambda expressions or closures, but not really,
# because they cannot make use of bindings in the environment. Or is
# there a way to access those?

def call_proc proc
  proc.call('test')
end

val_in_environment = 3
my_proc = lambda do
  |sth|
  puts "Hello, proc! #{sth} #{val_in_environment}"
end
call_proc(my_proc)

# Works.

def call_lambda lamb
  lamb.call('test')
end

val_in_environment = 3
my_lambda = lambda do
  |sth|
  puts "Hello, lambda! #{sth} #{val_in_environment}"
end
call_lambda(my_lambda)

# Works.

##############
# Superclass #
##############

4.class
4.class.superclass
4.class.superclass.superclass
4.class.superclass.superclass.superclass
4.class.superclass.superclass.superclass.superclass

nil
  .class.superclass.superclass.superclass
  .class.superclass.superclass.superclass
  .class.superclass.superclass.superclass
  .class.superclass.superclass.superclass
  .class.superclass.superclass.superclass
  .class.superclass.superclass.superclass
  .class.superclass.superclass.superclass

# ... for as long as you want.

#######################
# Define a tree class #
#######################

class Tree
  # Apparently we can declare accessible fields like this,
  # some meta programming stuff going on here, building
  # accessors (and setter?) out of symbols. A shortcut.
  attr_accessor :children, :node_name

  # constructor - is the name a convention or language built-in?
  # default arguments
  def initialize(name, children=[])
    # apparently accessing field inside the class can be
    # done with `@`. Similar to Python's `self.children`,
    # just shorter.
    @children = children
    @node_name = name
  end

  def visit_all(&block)
    # Visit this one child.
    visit(&block)
    # Visit its children.
    children.each do
      |child|
      child.visit_all(&block)
    end
  end

  def visit(&block)
    block.call self
  end
end

# -> This example shows how the visitor pattern can be
# simply done by block given as an argument, similar to how
# it can be done in functional languages, by simply giving a
# function as an argument.

ruby_tree = Tree.new(
  "Ruby",
  [Tree.new("Reia"),
   Tree.new("MacRuby")])

puts "Visiting a node"
ruby_tree.visit do
  |node|
  puts "node name: #{node.node_name}"
end
puts ""


puts "Visiting all nodes in the tree"
ruby_tree.visit_all do
  |node|
  puts "node name: #{node.node_name}"
end
puts ""

#############################
# READING AND WRITING FILES #
#############################

# Ruby uses modules to define functions, which then can be
# added to any class. -- What happens when 2 modules have
# the same names for any of their functions?

module ToFile
  def filename
    "object_#{self.object_id}.txt"
  end
  def to_file
    File.open(filename, 'w') do
      |file|
      # to_s is defined later, inside the class. The module
      # can already reference it, as the reference will be
      # resolved at runtime. There is no check for the
      # function to exist at compile time. Suppose the
      # method `to_s` was never implemented in the class. In
      # that case, if the function `to_file` never gets
      # called, then there would be no error. The "contract"
      # is implicit. By convention you will have to
      # implement the referenced methods.
      file.write(to_s)
    end
  end
end

# Now create a class, which will get the functions from the
# ToFile module.

class Person
  # Here we add the functions from the ToFile module.
  include ToFile

  attr_accessor :name

  def initialize (name)
    @name = name
  end

  # The definition of to_s, referenced from the module.
  def to_s
    name
  end
end

Person.new('matz').to_file

# One might call the module a "mixin", as it adds the
# capability of writing an object to a file, if only the
# object has a set of methods, which the module makes use
# of.

# Lets try modules with functions of equal name.

module A
  def foo name
    puts "Foo #{name}!"
  end

  def foobar name
    puts "Foobar-foo #{name}!"
  end
end

module B
  def bar name
    puts "Foo #{name}!"
  end

  def foobar name
    puts "Foobar-bar #{name}!"
  end
end

class AB
  include A
  include B
end

AB.new.foobar("Herbert")
# Foobar-bar Herbert!
# => nil

# Apparently the implementation of `foobar` of the last
# include is used.

# Includes are inherited.

# It seems, that `include` behaves, as if simple the
# module's text was copied into the class definition. But
# maybe there is more logic to it than that.

########################################
# Standard Library Mixins / Interfaces #
########################################

puts "#{'a' <=> 'b'}"
lst =  [2,547,23,6]
